#pragma once

#include "concepts.h"
#include "previous.h"
#include <coroutine>
#include <exception>
#include <source_location>
#include <system_error>

namespace co_async {
template <class T> struct Promise {
  auto initial_suspend() { return std::suspend_always(); }

  auto final_suspend() noexcept { return PreviousAwaiter(m_previous); }

  auto get_return_object() {
    return std::coroutine_handle<Promise>::from_promise(*this);
  }

  void unhandled_exception() {
    // std::cout << "unhandled_exception" << std::endl;
    m_exception = std::current_exception();
    std::rethrow_exception(m_exception);
  }

  void return_value(T &&val) { m_result.put(std::move(val)); }
  void return_value(const T &val) { m_result.put(val); }

  T result() {
    if (m_exception) {
      std::rethrow_exception(m_exception);
    }
    // std::cout << "move2" << std::endl;

    return m_result.move();
  }

  std::coroutine_handle<> m_previous;
  std::exception_ptr m_exception;

  Promise &operator=(Promise &&) = delete;
  UnInit<T> m_result;
};

template <> struct Promise<void> {

  auto initial_suspend() { return std::suspend_always(); }

  auto final_suspend() noexcept { return PreviousAwaiter(m_previous); }

  auto get_return_object() {
    return std::coroutine_handle<Promise>::from_promise(*this);
  }

  void unhandled_exception() {
    m_exception = std::current_exception();
    std::rethrow_exception(m_exception);
    // std::cout << "unhandled_exception" << std::endl;
  }

  void return_void() { m_exception = nullptr; }

  auto result() {
    if (m_exception) {
      std::rethrow_exception(m_exception);
    }
    return NonVoidHelper<>{};
  }

  std::coroutine_handle<> m_previous;
  std::exception_ptr m_exception;

  Promise &operator=(Promise &&) = delete;
};

template <class T = void, class P = Promise<T>>
class [[nodiscard("no co_await")]] Task {
public:
  using promise_type = P;
  Task(std::coroutine_handle<promise_type> co_handle) { m_handle = co_handle; }
  Task(Task &&that) : m_handle(that.m_handle) { that.m_handle = nullptr; };
  ~Task() {
    if (m_handle) {
      m_handle.destroy();
    }
  }
  auto operator co_await() const noexcept { return Awaiter{m_handle}; }
  operator std::coroutine_handle<>() const noexcept { return m_handle; }
  auto &get_handle() noexcept { return m_handle; }
  struct Awaiter {
    bool await_ready() const noexcept { return false; }

    std::coroutine_handle<>
    await_suspend(std::coroutine_handle<> coroutine) const noexcept {
      m_handle.promise().m_previous = coroutine;
      return m_handle;
    }

    auto await_resume() const { return m_handle.promise().result(); }
    std::coroutine_handle<promise_type> m_handle;
  };

  std::coroutine_handle<promise_type> m_handle;
};

using PreHandleTask = Task<void, PreviousPromise>;

inline auto
check_error(auto res,
            const std::source_location &loc = std::source_location::current()) {
  if (res == -1) [[unlikely]] {
    throw std::system_error(errno, std::system_category(),
                            std::string(loc.file_name()) + ":" +
                                std::to_string(loc.line()));
  }
  return res;
}

inline auto check_uring_error(auto res, const std::source_location &loc =
                                            std::source_location::current()) {
  if (res < 0) [[unlikely]] {
    throw std::system_error(-res, std::system_category(),
                            std::string(loc.file_name()) + ":" +
                                std::to_string(loc.line()));
  }
  return res;
}

template <class P> struct GetCoroutine {
  bool await_ready() const noexcept { return false; }
  std::coroutine_handle<P>
  await_suspend(std::coroutine_handle<P> pre) noexcept {
    m_previous = pre;
    return pre;
  }
  std::coroutine_handle<P> await_resume() noexcept { return m_previous; }
  std::coroutine_handle<P> m_previous;
};

} // namespace co_async
